《ECMAScript 6入门》阅读笔记(上)
入门ES6最佳选择,读完收获颇多。但关注点主要还是放在一些新的编程模式,比如 Promise 和 Generator 带来的对前端编程逻辑的巨大改变(甚至我认为, ES6中 Promise和 Generator 才是主菜,其他不过是饭前甜点罢了)。除此以外,很多以前在 ES5 中蛋疼的实现有了更优雅的实现,比如块作用域和变量提升,函数中万变的 this 等。很多东西碍于实现,在前端中可能并不会全部用上,但是至少,配合 polyfill 和打包工具,让我们在工程化中有了更进一步的统一

let 和 const 命令 !
- 不存在变量提升
- 存在块级作用域
- 暂时性死区 => (
typeof操作符不再百分百安全) - 不允许重复声明
- 安全起见,不在作用域块中声明函数(浏览器实现)
const声明后不允许改变(对于引用类型,指地址不变)- ES6 中六种声明变量语法(
var、let、const、function、import、class) let和const声明的全局变量不再是顶级对象的属性
变量的解构赋值 !
- 数组:
let [a, b, c] = [1, 2, 3] - 对象:
let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; // foo => "aaa" - 等号邮编必须具备迭代器接口(如数组、对象)
- 允许指定默认值:
let [x, y = 'b'] = ['a']; // x => 'a', y => 'b' - 默认值的判断使用严格等于
undefined,如果默认值是一个表达式或函数,则进行惰性求值 - 字符串的解构赋值:
const [a, b, c, d, e] = 'hello' - 如果等号右边是不是对象或数组,则先转为对象,而
undefined和null无法转为对象导致报错
字符串的扩展 !
直接遍历方法
for...of123for (let key of 'foo') {console.log(key) // 'f','o','o'}includes(),startsWith(),endsWith()includes():返回布尔值,表示是否找到了参数字符串startsWith():返回布尔值,表示参数字符串是否在源字符串的头部endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部
repeat(n)返回一个新字符串,表示将原字符串重复n次padStart(),padEnd()字符串不够指定长度,会在头部或尾部补全- 模板字符串
foo ${fn()} bar-${name}
数值的扩展
Number.isFinite(),Number.isNaN()只对数值有效,不转换- 全局方法
parseInt()和parseFloat()移植到了Number对象上 Number.isInteger()用来判断一个值是否为整数- 新增一个极小的常量
Number.EPSILON,一般用来判断误差精度 Math.trunc(number)方法用于去除一个数的小数部分,返回整数部分Math.sign(number)方法用来判断一个数到底是正数、负数、还是零- 指数运算符1232 ** 2 // 42 ** 3 // 8a **= 2 // 等同于 a = a * a;
数组的扩展
Array.from(arrayLike, fn, scope)将类数组(具备Iterator接口,即具有length属性)对象转为真正的数组,类似Array.prototype.slice.call(arrayLike)Array.of方法用于将一组值,转换为数组 => 弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组[NaN].indexOf(NaN) // -1,而find()和findIndex()则可以找到NaNfill(times, start, end)方法使用给定值,填充一个数组entries(),keys(),values()分别遍历键值对、键名、键值- ES6 明确将稀疏数组的空位转为
undefined(ES5的迭代器方法则忽略空位)
函数的扩展 !
默认值
- 默认值 =>
function log(x, y = 'World') {}(参数变量是默认声明的,所以不能用let或const再次声明,也不能有同名参数) - 如果参数默认值是变量,那么参数就不是传值的,而是每次都重新计算默认值表达式的值
双重默认值配合解构赋值
1234function fetch (url, { body = '', method = 'GET' } = {}) {console.log(method)}fetch('http://example.com') // 可以不传入第二个参数同时有默认值定义默认值的参数应该是尾参数(不然没法省略,自然用不上默认值)
- 函数的
length属性不包含有默认值的形参(也不包含rest参数)123// 如果设置了默认值的参数不是尾参数,那么 length 属性也不再计入后面的参数了(function (a = 0, b, c) {}).length // 0(function (a, b = 1, c) {}).length // 1
rest参数
...rest参数可以替代arguments对象,是数组对象,可以直接使用数组方法...rest参数后面不能有其他参数,否则报错- 函数的
length属性不包含...rest参数
扩展运算符
...扩展运算符 => 将数组转化为逗号分离的参数,类似...rest参数的逆运算- 合并数组:
[1, 2, ...array] - 将字符串转成数组:
[...'hello'] // [ "h", "e", "l", "l", "o" ]
箭头函数
|
|
- 如果箭头函数直接返回一个对象,必须在对象外面加上括号
var getTempItem = id => ({ id: id, name: "Temp" }) this对象指向父级,不可以用new调用,没有arguments对象,不能使用yield,(实际上并没有自己的this)- 箭头函数没有自己的
this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向 - 多重嵌套函数(难点)
尾调用优化(ES6 的尾调用优化只在严格模式下开启)
尾调用(Tail Call)指某个函数的最后一步是调用另一个函数
function f(x) { return g(x) }
闭包一般是没有做到尾调用优化的,因为定义上不允许尾递归 => 尾调用自身,只要使用尾递归,永远不会发生“栈溢出”(stack overflow),函数编程可用递归实现循环(递归本质上是一种循环操作)
- 阶乘函数优化
- 斐波那契数列优化
- 柯里化和参数默认值
- 手动实现尾递归优化(核心是减少调用栈) => 使用循环代替递归
- 蹦床函数
其他
- 只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错
- 如果将一个匿名函数赋值给一个变量,ES5 的 name 属性,会返回空字符串,而 ES6 的 name 属性会返回实际的函数名
- 尾调用 => 某个函数的最后一步是调用另一个函数
- 函数调用自身,称为递归。如果尾调用自身,就称为尾递归
对象的扩展 !
属性的简洁表示法 => 简洁写法的属性名总是字符串
12345678910var birth = '2000/01/01';var Person = {name: '张三',//等同于 birth: birthbirth,// 等同于 hello: function ()...hello() {console.log('我的名字是', this.name);}};Object.is()类似全等符===,但+0不等于-0,NaN等于自身 =>Object.is('foo', 'foo')Object.assign方法用于对象的合并,将源对象的所有自身可枚举属性,复制到目标对象 =>Object.assign(target, source1, source2),浅复制- 属性的遍历
for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性。Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管是属性名是 Symbol 或字符串,也不管是否可枚举
Symbol
新的原始数据类型,表示独一无二的值,常用来作为对象属性名避免冲突
- Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述
- 可显示转换成字符串或者布尔值
作为对象属性名或在对象的内部定义属性时,Symbol 值必须放在方括号之中
1234567// 第一种写法var a = {};a[mySymbol] = 'Hello!';// 第二种写法var a = {[mySymbol]: 'Hello!'};Symbol 作为属性名,该属性不会出现在
for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。- 重用 Symbol 使用
Symbol.for,该方法生成的变量会登记在全局变量中
Set 和 Map 数据结构
Set
set 类似于数组,但是成员的值都是唯一的,没有重复的值
|
|
- Set 结构没有键名,只有键值(或者说键名和键值是同一个值)
Array.from方法可以将 Set 结构转为数组123456// 数组去重新方法1function dedupe(array) {return Array.from(new Set(array));}// 数组去重新方法2let unique = [...new Set(arr)]WeakSet 结构与 Set 类似,也是不重复的值的集合
- WeakSet 的成员只能是对象,而不能是其他类型的值
- WeakSet 中的对象都是弱引用,不计入垃圾回收机制,也是不可遍历的
Map
Map 类似于对象,但属性名不限制为字符串(可是是任何数据结构,包括对象),由此实现更强大的hash结构
|
|
- 注意,只有对同一个对象的引用,Map 结构才将其视为同一个键,同样的值的两个实例,在 Map 结构中被视为两个键
- WeakMap结构与Map结构类似,也是用于生成键值对
- WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
- WeakMap的键名所指向的对象,不计入垃圾回收机制
Proxy和Reflect
Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,属于一种“元编程”
var proxy = new Proxy(target, handler);
Reflect
与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API,而且它与 Proxy 对象的方法是一一对应的
将 Object 对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到 Reflect 对象上
修改某些 Object 方法的返回结果,让其变得更合理
编程风格
- 优先使用
const、然后是let,不用var,避免变量泄露和提升 - 使用解构赋值声明多个变量 =>
const [a, b, c] = [1, 2, 3] - 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
- 单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾
- 使用扩展运算符
...拷贝数组 - 如果模块默认输出一个函数,函数名的首字母应该小写,如果模块默认输出一个对象,对象名的首字母应该大写